using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using SimpleMessageQueue;
using slcl.Packets;

namespace slcl
{
    public struct PacketCarrier
    {
        public Packet packet;
        public Simulator sim;
    } 

    /// <summary>
    /// The logical connection between a simulator and the physical server.
    /// By runing each sim on it's own thread we can respond quickly to netowrk IO 
    /// wether the client is busy or not. Using the MessageQueue allows us to remain
    /// effectively isolated from other threads and object logic so that
    /// we aren't off in lala land when entwork IO comes in.
    /// We really should set any thread that runs this object to a higher
    /// priority than other threads in the app.
    /// </summary>

    public class SimulatorConnection
    {

        class PacketReadCarrier
        {
            public byte[] Buffer;
            public int BytesRead;

            public PacketReadCarrier()
            {
                Buffer = new byte[2048];
                BytesRead = -1;
            }
        }

        public enum Commands
        {
            Send,
            Connect,
            Disconnect,
            Shutdown
        }

        public enum Events
        {
            Packet,
            Connected,
            Disconnected,
            Shutdown,
            StatusUpdate,
            UnknownPacketRecieved, // returns any packet that isn't decoded.
            Log
        }

        /// <summary>
        /// The sim data that we collect.
        /// </summary>
        Simulator Sim;

        /// <summary>
        /// The grid that this sim is a part of.
        /// </summary>
        Grid Grid;  

        /// <summary>
        /// copied from Grid.Client
        /// </summary>
        Settings Settings;
        /// <summary>
        /// handles action requests from Client or NetowrkManager
        /// </summary>
        public MessageQueue MQueue;

        /// <summary>
        /// Obvious. Are we connected or not?
        /// </summary>
        public bool Connected = false;

        /// <summary>
        /// have we timed out and need cleaning?
        /// </summary>
        public bool DisconnectCandidate = false;

        /// <summary>
        /// narf! Set once we get established with our sim.
        /// </summary>
        public ManualResetEvent ConnectedEvent;

        /// <summary>
        /// for blocking the next call to reading from the buffer.
        /// </summary>
        private object PacketReadLock;

        /// <summary>
        /// stats stats everyone loves stats.....
        /// </summary>
        public ulong SentPackets = 0, RecvPackets = 0;
        public ulong SentBytes = 0, RecvBytes = 0;
        public int ConnectTime = 0, ResentPackets = 0;
        public int SentPings = 0, ReceivedPongs = 0;
        // Calculated bandwidth -- it would be nice to have these calculated
        // on the fly, but this is far, far easier :/  These are bytes per second.
        public int IncomingBPS = 0, OutgoingBPS = 0;

        /// <summary>
        /// Packet sequence counter. SO we can tell if things get out of order.
        /// </summary>
        private uint Sequence = 0;
        private object SequenceLock = new object();

        /// <summary>
        /// Buffer for receiving packets on.
        /// Turns out, .NET does not mash UDP togeather so we're assumign that it's one call one packet
        /// This MAY or MAY NOT be the case. Experimentation will tell.
        /// </summary>
       // private byte[] RecvBuffer = new byte[8192];
       // private byte[] ZeroBuffer = new byte[8192];
        private byte[] ZeroOutBuffer = new byte[8192];

        /// <summary>
        /// packets taht have been read in from the Grid
        /// </summary>
        private Queue<PacketReadCarrier> ReadPackets;

        /// <summary>
        /// our precious
        /// </summary>
        private Socket Connection;

        // Packets we sent out that need ACKs from the simulator
        private Dictionary<uint, Packet> NeedAck = new Dictionary<uint, Packet>();

        // Sequence numbers of packets we've received from the simulator
        private Queue<uint> Inbox;//, PingTimes;

        /// <summary>
        /// Used for stats collection I believe
        /// </summary>
        private Queue<ulong> InBytes, OutBytes;

        // ACKs that are queued up to be sent to the simulator
        private SortedList<uint, uint> PendingAcks = new SortedList<uint, uint>();

        /// <summary>
        /// Where our physical sim server is at.
        /// </summary>
        public  IPEndPoint ipEndPoint;        
        /// <summary>
        /// conversion to udp style for actual sends.
        /// </summary>
        private EndPoint endPoint;

        /// <summary>
        /// Handles acks and resends on the Grid.
        /// </summary>
        private System.Timers.Timer AckTimer;
        /// <summary>
        /// Pings our Sim to make certain that we stay alive.
        /// </summary>
        private System.Timers.Timer PingTimer;
        /// <summary>
        /// Gathers stats for us.
        /// </summary>
        private System.Timers.Timer StatsTimer;
        /// <summary>
        /// Periodicly marks us as a candidate for disconnect.
        /// receiving a packet unmarks us.
        /// </summary>
        private System.Timers.Timer ConnectionTimeout;

        /* Ping processing, to measure link health */
        public int LastPingSent = 0;
        public byte LastPingID = 0;
        public int LastLag = 0;
        public int MissedPings = 0;

        private Dictionary<IAsyncResult, PacketReadCarrier> ReadCalls;
        Thread PacketDecoderThread;
        bool ProcessPackets = false;
        Queue<PacketReadCarrier> FreeCarriers;

        /// <summary>
        /// We setup a new queue and a thread to run that queue on.
        /// Upon connection start we send an Ack with the Wait handle
        /// that static.sprocket advocates to Grid manager.
        /// 
        /// TODO implement async login.
        /// 
        /// Peer into CommandMessage for this wait object.
        /// Support isn't complete just yet, but it's getting close. :)
        /// 
        /// </summary>
        /// <param name="c">Client that is making this link</param>
        /// <param name="s">Sim that we are conencting to</param>
        public SimulatorConnection(Grid g, Simulator s, IPAddress ip, ushort port)
        {
            ipEndPoint = new IPEndPoint(ip, (int)port);
            Init(g,s);
        }

        public SimulatorConnection(Grid g, Simulator s, IPEndPoint ep)
        {
            ipEndPoint = ep;
            Init(g,s);
        }

        void Init(Grid g, Simulator s)
        {
            Sim = s;
            Grid = Sim.Grid;
            Settings = Grid.Client.Settings;
            Connection = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            endPoint = (EndPoint)ipEndPoint;

            //FIXME Estate = new EstateTools(Client);
            ConnectedEvent = new ManualResetEvent(false);

            // Create 256 packet carriers and put them in the queue.
            FreeCarriers = new Queue<PacketReadCarrier>(256);
            for (int x = 0; x < 256; x++)
                FreeCarriers.Enqueue(new PacketReadCarrier());

            ReadCalls = new Dictionary<IAsyncResult, PacketReadCarrier>();
            ReadPackets = new Queue<PacketReadCarrier>();
            PacketReadLock = new object();

            Inbox = new Queue<uint>(Settings.INBOX_SIZE);
            InBytes = new Queue<ulong>(Settings.STATS_QUEUE_SIZE);
            OutBytes = new Queue<ulong>(Settings.STATS_QUEUE_SIZE);
            //PingTimes = new Queue<uint>(Client.Settings.STATS_QUEUE_SIZE); //TODO: Is this here for a purpose?


            // for our inbound async write requests
            // sync writes will be handled with a direct call to SendPacket()
            MQueue = new MessageQueue(this, true);
            MQueue.RegisterPacketCallback(Message.MessageType.Command
                                        , new MessageQueue.MessageCallback(HandleCommand));

            // register connection management types of packets.

            // Create an endpoint that we will be communicating with (need it in two 
            // types due to .NET weirdness)

            ConnectionTimeout = new System.Timers.Timer(30000);
            ConnectionTimeout.Elapsed += new System.Timers.ElapsedEventHandler(ConnectionTimeout_Elapsed);

            AckTimer = new System.Timers.Timer(Settings.Grid_TICK_LENGTH);
            AckTimer.Elapsed += new System.Timers.ElapsedEventHandler(AckTimer_Elapsed);

            StatsTimer = new System.Timers.Timer(1000);
            StatsTimer.Elapsed += new System.Timers.ElapsedEventHandler(StatsTimer_Elapsed);

            PingTimer = new System.Timers.Timer(Settings.PING_INTERVAL);
            PingTimer.Elapsed += new System.Timers.ElapsedEventHandler(PingTimer_Elapsed);

        }


        /// <summary>
        /// Checks to see if we received any pings back within our timeframe.
        /// if not then we toast this sim link and let Grid know.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ConnectionTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (!ProcessPackets)
            {
                Log("Connection timout left running on " + Sim.ToString(), LogLevel.Warning);
                ConnectionTimeout.Stop();
                return;
            }
            if (DisconnectCandidate)
                Disconnect();
            DisconnectCandidate = true;  
        }

        public void Log(string msg, LogLevel lvl)
        {
            Sim.Log("Conn: " + msg, lvl);
        }


        /// <summary>
        /// Our real conenction method for getting hooked up to a simulator.
        /// This used to reside in NetowrkManager. However because 99% of what
        /// we do is on a per sim basis and the connections are on a per sim basis
        /// then it should realisticly reside within a connection class associated 
        /// tightly with a simulator object. One such as SimulatorConnection. this!
        /// </summary>
        public bool Connect()
        {
            if(Connected)
                Disconnect();
            // Start the timers

            Log("Connecting to " + ipEndPoint.ToString(), LogLevel.Info);

            try
            {
                ConnectedEvent.Reset(); // we wait for regionhandshake handler to set this.

                // Associate this simulator's socket with the given ip/port and start listening
                Connection.Connect(endPoint);

                // start the packet processor.
                ProcessPackets = true;
                if (null == PacketDecoderThread)
                {
                    Log("Starting decoder thread.", LogLevel.Debug);
                    PacketDecoderThread = new Thread(this.RunPacketProcessing);
                    PacketDecoderThread.IsBackground = true;
                    PacketDecoderThread.Name = "PacketDecoder " + this.ToString();
                    PacketDecoderThread.Start();
                }

                if (!ReadStart())
                {
                    Log("Failed to start the async read event chain on connect()", LogLevel.Error);
                    Disconnect();
                    return false;
                }
                PingTimer.Start();
                AckTimer.Start();
                StatsTimer.Start();
                ConnectionTimeout.Start();

                // Send the UseCircuitCode packet to initiate the connection
                UseCircuitCodePacket use = new UseCircuitCodePacket();
                use.CircuitCode.Code = Grid.CircuitCode;
                use.CircuitCode.ID = Grid.AgentID;
                use.CircuitCode.SessionID = Grid.SessionID;

                // Send the initial packet out async without a callback.                
                SendPacket(use);
                ConnectTime = Environment.TickCount;
                return true;
            }
            catch (Exception e)
            {
                Log(e.ToString(), LogLevel.Error);
                Disconnect();
            }

            return false;
        }


        /// <summary>
        /// FIXME: Implement....
        /// </summary>
        /// <returns></returns>
        public bool Disconnect()
        {
            if (!Connected)
            {
                Log("Attempted disconenct for not-conencted sim " + Sim.ToString(), LogLevel.Warning);
                return false;
            }
            Log("Closing socket for " + Sim.ToString(), LogLevel.Info);
            Connected = false;
            ProcessPackets = false;
            PingTimer.Stop();
            AckTimer.Stop();
            StatsTimer.Stop();
            ConnectionTimeout.Stop();
            // check to see if we are the last connection before disconencting...
            lock (ReadPackets)
            {
                Monitor.PulseAll(ReadPackets);
            }
            Connection.Close(5);

            if (null != PacketDecoderThread)
            {
                Log("Join()ing PacketDecoder Thread for " + Sim.ToString(), LogLevel.Info);
                if (!PacketDecoderThread.Join(5000))
                {
                    Log("Timed out waiting for PacketDecoder Thread to rejoin for " + Sim.ToString(), LogLevel.Warning);
                    PacketDecoderThread.Abort();
                    if (!PacketDecoderThread.Join(5000))
                    {
                        Log("Failed to rejoin PacketDecoder thread after abort for " + Sim.ToString(), LogLevel.Critical);
                    }
                }
            }
            PacketDecoderThread = null;
            Inbox.Clear();
            SentPackets = 0;
            RecvPackets = 0;
            SentBytes = 0;
            RecvBytes = 0;
            ConnectTime = 0;
            ResentPackets = 0;
            SentPings = 0;
            ReceivedPongs = 0;
            IncomingBPS = 0;
            OutgoingBPS = 0;
            Sequence = 0;
            InBytes.Clear();
            OutBytes.Clear();
            PendingAcks.Clear();            
            Grid.SimDisconnected(Sim);
           // Sim.RemoveConnection();
            return true;
        }

        public void HandleCommand(Message msg)
        {
            CommandMessage Msg = (CommandMessage)msg;
            switch ((Commands)Msg.Command)
            {
                case Commands.Send:
                    if(msg.Data is Packet)
                    {
                        SendPacket((Packet)msg.Data, true);
                        MQueue.Send(Msg.AckCommand(AckMessage.StatusType.Completed));
                    }
                    break;
                case Commands.Connect:
                    Connect();
                    MQueue.Ack(Msg, (Connected ? AckMessage.StatusType.Completed : AckMessage.StatusType.Failed));
                    break;
                case Commands.Disconnect:
                    Disconnect();
                    MQueue.Ack(Msg, AckMessage.StatusType.Completed);
                    break;
            }
        }

        /// <summary>
        /// for direct use of the Grid layer sending.
        /// Please don't use this unless you KNOW that you need to use it. (Like AgentThrottle)
        /// </summary>
        /// <param name="packet"></param>
        public void SendPacket(Packet packet)
        {
            SendPacket(packet, true);
        }

        /// <summary>
        /// Sends a packet.
        /// 
        /// I *think* that there is the potential for this to backlog the connection
        /// due to massive outbound packet size (texture uploads? no idea what would be that large)
        /// So we should keep our eyes open here.
        /// 
        /// </summary>
        /// <param name="packet">Packet to be sent</param>
        /// <param name="incrementSequence">Increment sequence number?</param>
        public void SendPacket(Packet packet, bool incrementSequence)
        {
            byte[] buffer;
            int bytes;

  //          Log("Sending packet " + packet.Type.ToString(), LogLevel.Info);

            // Keep track of when this packet was sent out
            packet.TickCount = Environment.TickCount;

            #region Sequence and ack tagging
            if (incrementSequence)
            {
                // Set the sequence number
             //   Log("send: lock sequencelock", LogLevel.Warning);
                lock (SequenceLock)
                {
                    if (Sequence > Settings.MAX_SEQUENCE)
                        Sequence = 1;
                    else
                        Sequence++;
                    packet.Header.Sequence = Sequence;
                }
             //   Log("Send: Release sequencelock", LogLevel.Warning);
                // Scrub any appended ACKs since all of the ACK handling is done here
                if (packet.Header.AckList.Length > 0)
                    packet.Header.AckList = new uint[0];

                packet.Header.AppendedAcks = false;

                if (packet.Header.Reliable)
                {
              //      Log("Send: Lock needack", LogLevel.Warning);
                    lock (NeedAck)
                    {
                        if (!NeedAck.ContainsKey(packet.Header.Sequence))
                        {
                            NeedAck.Add(packet.Header.Sequence, packet);
     //                       Log("Need ack for " + packet.Header.Sequence.ToString(), LogLevel.Debug);
                        }
                        else
                            Log("Attempted to add a duplicate sequence number (" +
                                packet.Header.Sequence + ") to the NeedAck dictionary for packet type " +
                                packet.Type.ToString(), LogLevel.Warning);
                    }
               //     Log("Send: Release NeedAck", LogLevel.Warning);
                    // Don't append ACKs to resent packets, in case that's what was causing the
                    // delivery to fail
                    if (!packet.Header.Resent)
                    {
                        // Append any ACKs that need to be sent out to this packet
                //        Log("Send: Lock pendingacks", LogLevel.Warning);
                        lock (PendingAcks)
                        {
                            if (PendingAcks.Count > 0 && PendingAcks.Count < Settings.MAX_APPENDED_ACKS &&
                                packet.Type != PacketType.PacketAck &&
                                packet.Type != PacketType.LogoutRequest)
                            {

                                packet.Header.AckList = new uint[PendingAcks.Count];

                                for (int i = 0; i < PendingAcks.Count; i++)
                                    packet.Header.AckList[i] = PendingAcks.Values[i];

                                PendingAcks.Clear();
                                packet.Header.AppendedAcks = true;
                            }
                        }
               //         Log("Send: Release PendingAck", LogLevel.Warning);
                    }
                }
            }

            #endregion

            #region Serialize and send

            buffer = packet.ToBytes();
            bytes = buffer.Length;
            SentBytes += (ulong)bytes;
            SentPackets++;

            try
            {
                // Zerocode if needed
                if (packet.Header.Zerocoded)
                {
                    lock (ZeroOutBuffer)
                    {
                        bytes = Helpers.ZeroEncode(buffer, bytes, ZeroOutBuffer);
                        WriteStart(ZeroOutBuffer, 0, bytes);
                    }
                }
                else
                {
                    WriteStart(buffer, 0, bytes);
                }
            }
            catch (SocketException)
            {
                Log("Tried to send a " + packet.Type.ToString() + " on a closed socket, shutting down " +
                    this.ToString(), LogLevel.Info);

                Disconnect();
                return;
            } 
            #endregion
        }

        /// <summary>
        /// Send a raw byte array payload as a packet
        /// </summary>
        /// <param name="payload">The packet payload</param>
        /// <param name="setSequence">Whether the second, third, and fourth bytes
        /// should be modified to the current stream sequence number</param>
        public void SendPacket(byte[] payload, bool setSequence)
        {
            if (Settings.OUTBOUND_THROTTLE) DoThrottle();

            try
            {
                if (setSequence && payload.Length > 3)
                {
                //    Log("SendByte: Lock sequence", LogLevel.Warning);
                    lock (SequenceLock)
                    {
                        payload[1] = (byte)(Sequence >> 16);
                        payload[2] = (byte)(Sequence >> 8);
                        payload[3] = (byte)(Sequence % 256);
                        Sequence++;
                    }
                //    Log("SendByte: Release sequence", LogLevel.Warning);

                }

                SentBytes += (ulong)payload.Length;
                SentPackets++;
                WriteStart(payload, 0 , payload.Length);
            }
            catch (SocketException)
            {
                Log("Tried to send a " + payload.Length + " byte payload on a closed socket, shutting down " +
                    this.ToString(), LogLevel.Info);

                Disconnect();
                return;
            }
        }


        /// <summary>
        /// queues up a read...
        /// </summary>
        bool ReadStart()
        {
            if (!ProcessPackets)
            {
                Log("Not starting read. ProcessPackets == " + ProcessPackets.ToString(), LogLevel.Warning);
                return false;
            }
            try
            {
                // if we are low, create 16 more. Shouldn't get this bad really....
                if (FreeCarriers.Count < 16)
                {
                    Log("WARNING: Free PacketReadCarrier count is low! Creating 16 more.", LogLevel.Warning);
                    for (int x = 0; x < 8; x++)
                        FreeCarriers.Enqueue(new PacketReadCarrier());
                }
                PacketReadCarrier carrier = FreeCarriers.Dequeue();
                // set us up to get new data while we handle this data.
                lock (ReadCalls)
                {
                    IAsyncResult result = Connection.BeginReceiveFrom(carrier.Buffer, 0, carrier.Buffer.Length
                    , SocketFlags.None, ref endPoint, new AsyncCallback(ReadEnd), carrier);
                    ReadCalls.Add(result, carrier);
                }
            }
            catch (Exception e)
            {
                Log("Exception in ReadStart!" + e.ToString(), LogLevel.Critical);
                Disconnect();
                return false;
            }
            return true;
        }            


        /// <summary>
        /// handles the result of the async read request.
        /// This should be on a thread from the IO thread pool.
        /// So to make things happen quickly, we toss to someone 
        /// else's SMQ object and go on with our life.
        /// </summary>
        /// <param name="result"></param>
        private void ReadEnd(IAsyncResult result)
        {
            try
            {
                if (!ProcessPackets)
                {
                    Log("Dropping last readend call", LogLevel.Warning);
                    return;
                }
                PacketReadCarrier carrier;
           //     Log("readend: Locking ReadCalls.", LogLevel.Warning);
                lock (ReadCalls)
                {
                    DisconnectCandidate = false;
                    if (!ReadCalls.ContainsKey(result))
                    {
                        Log("Failed to find the associated IAsyncResult entry in queue!", LogLevel.Error);
                        return;
                    }
                    carrier = ReadCalls[result];
                    ReadCalls.Remove(result);
                    if (ProcessPackets)
                    {
                //        Log("ReadEnd: starting read.", LogLevel.Debug);
                        ReadStart();
                    }
                    carrier.BytesRead = Connection.EndReceiveFrom(result, ref endPoint);
                }
           //     Log("readend: releasing readcalls.", LogLevel.Warning);


                // Update the disconnect flag so this sim doesn't time out
             //   Log("readend: Locking readpackets.", LogLevel.Warning);
                lock (ReadPackets)
                {
                    ReadPackets.Enqueue(carrier);
                //    Log("ReadEnd: Packet read.", LogLevel.Debug);
                    Monitor.Pulse(ReadPackets);
                }
             //   Log("readend: Releasing readpackets.", LogLevel.Warning);
            }
            catch (Exception except)
            {
                Log(except.ToString(), LogLevel.Critical);
                Disconnect();
                return;
            }
        }

        /// <summary>
        /// processes packets from the recievedpacket queue and pushes them to Grid for processing.
        /// </summary>
        void RunPacketProcessing()
        {
            byte[] ZeroBuffer = new byte[8192];
            try
            {
                Log("PacketDecoder thread started.", LogLevel.Debug);
                while (ProcessPackets)
                {
                    bool RetrievedPacket = false;
                    Packet packet = null;
                    PacketReadCarrier carrier = null;
                    int packetEnd = -1;
                  //  Log("DECODER: Locking readpackets.", LogLevel.Warning);
                    lock (ReadPackets)
                    {
                        // Retrieve the incoming packet
                        try
                        {
                            while (ReadPackets.Count == 0 && ProcessPackets)
                            {
                                Monitor.Wait(ReadPackets, 250);
                            }
                            // check again, this COULD be a bad signaling...
                            if (ReadPackets.Count > 0)
                            {
                                carrier = ReadPackets.Dequeue();
                                RetrievedPacket = true;
                            }
                        }
                        catch (Exception e)
                        {
                            Log("decoder: Death at exception in while!", LogLevel.Warning);
                            Log(e.ToString(), LogLevel.Critical);
                            Disconnect();
                            return;
                        }
                    }
                   // Log("Decoder: Release Readpackets", LogLevel.Warning);
                    if (RetrievedPacket)
                    {
                        #region Packet Decoding
                        packetEnd = carrier.BytesRead - 1;
                        // pull the packet out of the receive buffer, if there is anything there...
                        packet = Packet.BuildPacket(carrier.Buffer, ref packetEnd, ZeroBuffer);

                        // Fail-safe check
                        if (packet == null)
                        {
                            Log("Couldn't build a message from the incoming data", LogLevel.Warning);
                            MQueue.MatchRegistered(Events.UnknownPacketRecieved, AckMessage.StatusType.Completed, carrier);
                        }
                        else
                        {
                       //     Log("Received Packet type " + packet.Type.ToString(), LogLevel.Debug);
                            RecvBytes += (ulong)carrier.BytesRead;
                            RecvPackets++;

                        #endregion Packet Decoding

                            // since acks are important, we want to send() our ack right away.
                            #region Reliable Ack Sending

                            if (packet.Header.Reliable)
                            {
                                // Queue up ACKs for resent packets
                        //        Log("DECODER:  locking pendingacks", LogLevel.Warning);
                                lock (PendingAcks)
                                {
                                    uint sequence = (uint)packet.Header.Sequence;
                           //         Log("Storing ack " + sequence.ToString(), LogLevel.Debug);
                                    if (!PendingAcks.ContainsKey(sequence)) PendingAcks[sequence] = sequence;
                                }
                       //         Log("DECODER:  releasing PendingAcks", LogLevel.Warning);
                                // Send out ACKs if we have a lot of them
                                // performance tweak. This means that we fell behind! 
                                if (PendingAcks.Count >= Settings.MAX_PENDING_ACKS)
                                    SendAcks();
                            }

                            #endregion Reliable Handling

                            #region Inbox Tracking

                            // The Inbox doesn't serve a functional purpose any more, it's only used for debugging
                       //     Log("Decoder: lock Inbox", LogLevel.Warning);
                            lock (Inbox)
                            {
                                // Check if we already received this packet
                                if (Inbox.Contains(packet.Header.Sequence))
                                {
                                    Log("Received a duplicate " + packet.Type.ToString() + ", sequence=" +
                                        packet.Header.Sequence + ", resent=" + ((packet.Header.Resent) ? "Yes" : "No") +
                                        ", Inbox.Count=" + Inbox.Count + ", NeedAck.Count=" + NeedAck.Count
                                        , LogLevel.Info);

                                    // Avoid firing a callback twice for the same packet
                                    return;
                                }
                                else
                                {
                                    // Keep the Inbox size within a certain capacity
                                    while (Inbox.Count >= Settings.INBOX_SIZE)
                                    {
                                        Inbox.Dequeue(); Inbox.Dequeue();
                                        Inbox.Dequeue(); Inbox.Dequeue();
                                    }

                                    // Add this packet to the inbox
                                    Inbox.Enqueue(packet.Header.Sequence);
                                }
                            }
                       //     Log("Decoder: Release inbox", LogLevel.Warning);
                            #endregion Inbox Tracking

                            #region ACK handling

                            // Handle appended ACKs
                            if (packet.Header.AppendedAcks)
                            {
                        //        string Acks = "Received acks: ";
                       //         Log("DECODER:  locking Needack", LogLevel.Warning);
                                lock (NeedAck)
                                {
                                    for (int i = 0; i < packet.Header.AckList.Length; i++)
                                    {
                                        NeedAck.Remove(packet.Header.AckList[i]);
                               //         Acks += packet.Header.AckList[i].ToString() + " ";
                                    }
                                }
                       //         Log("DECODER: Releasing NeedAck", LogLevel.Warning);
                            //    Log(Acks, LogLevel.Debug);
                            }

                            // Handle PacketAck packets
                            if (packet.Type == PacketType.PacketAck)
                            {
                                PacketAckPacket ackPacket = (PacketAckPacket)packet;
                       //         string Acks = "Received acks: ";
                     //           Log("DECODER: Lock NeedAck", LogLevel.Warning);
                                lock (NeedAck)
                                {
                                    for (int i = 0; i < ackPacket.Packets.Length; i++)
                                    {
                                        NeedAck.Remove(ackPacket.Packets[i].ID);
                         //               Acks += ackPacket.Packets[i].ID.ToString() + " ";
                                    }
                                }
                     //           Log("DECODER: releasing NeedAck", LogLevel.Warning);
                        //        Log(Acks, LogLevel.Debug);
                            }

                            #endregion ACK handling

                            #region FireCallbacks

                            // local handlers so we avoid the queues...

                            if (packet.Type == PacketType.RegionHandshake)
                                RegionHandshakeHandler(packet);
                            else if (packet.Type == PacketType.StartPingCheck)
                                StartPingCheckHandler(packet);
                            else if (packet.Type == PacketType.CompletePingCheck)
                                PongHandler(packet);
                            else if (packet.Type == PacketType.ParcelOverlay)
                                ParcelOverlayHandler(packet);
                            else if (packet.Type == PacketType.SimStats)
                                SimStatsHandler(packet);
                            else if (packet.Type == PacketType.SimulatorViewerTimeMessage)
                                TimeMessageHandler(packet);
                            else if (packet.Type == PacketType.AgentMovementComplete)
                                MovementCompleteHandler(packet);
                            else if (packet.Type == PacketType.CoarseLocationUpdate)
                                CoarseLocationUpdateHandler(packet);
                            else if (packet.Type == PacketType.AgentMovementComplete)
                                MovementCompleteHandler(packet);

                            // now let the Grid handle the packet
                            Grid.DeliverInbound(packet, Sim);
                            #endregion FireCallbacks
                        }
                        // and put it back into the available queue.
                        FreeCarriers.Enqueue(carrier);
                        carrier = null;
                     //   Log("Decoder: Packet done. requeueing packet carrier.", LogLevel.Debug);
                    }
                    else
                    {
                        Log("Signaled without carrier in queue!", LogLevel.Warning);
                    }
                }
            }
            catch (Exception except)
            {
                Log(except.ToString(), LogLevel.Critical);
            }
            Log("PacketDecoder: Exiting.", LogLevel.Debug);
        }
        

        /// <summary>
        /// Queues up a write.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        bool WriteStart(byte[] buffer, int offset, int length)
        {
            if (!ProcessPackets)
            {
                Log("Attempt to write to a closed socket on " + Sim.ToString() + " Using " + this.ToString(), LogLevel.Warning);
                return true;
            }
            try
            {
           //     Log("Sending packet to " + this.ToString(), LogLevel.Info);
                Connection.BeginSendTo(buffer, offset, length, SocketFlags.None, endPoint, new AsyncCallback(WriteEnd), null);
            }
            catch (Exception e)
            {
                Log("Exception in WriteStart!" + e.ToString(), LogLevel.Error);
                Disconnect();
                return false;
            }
            return true;
        }

        private void WriteEnd(IAsyncResult result)
        {
           // Log("Packet sent to " + this.ToString(), LogLevel.Info);
            // FIXME: what goes here again? :)
        }

        /// <summary>
        /// Yawn
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ea"></param>
        private void AckTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ea)
        {
            if (!ProcessPackets)
            {
                Log("AckTimer left running when !ProcessingPackets for " + Sim.ToString(), LogLevel.Warning);
                AckTimer.Stop();
                return;
            }
       //     Log("Acks", LogLevel.Debug);
            SendAcks();
            ResendUnacked();
        }

        /// <summary>
        /// oooh.... stats. Yummy candy.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ea"></param>
        private void StatsTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ea)
        {
            if (!ProcessPackets)
            {
                Log("StatsTimer left running when !ProcessingPackets for " + Sim.ToString(), LogLevel.Warning);
                StatsTimer.Stop();
                return;
            }
      //      Log("Stats", LogLevel.Debug);
            ulong old_in = 0, old_out = 0;
            if (InBytes.Count >= Settings.STATS_QUEUE_SIZE)
                old_in = InBytes.Dequeue();
            if (OutBytes.Count >= Settings.STATS_QUEUE_SIZE)
                old_out = OutBytes.Dequeue();
            InBytes.Enqueue(RecvBytes);
            OutBytes.Enqueue(SentBytes);
            if (old_in > 0 && old_out > 0)
            {
                IncomingBPS = (int)(RecvBytes - old_in) / Settings.STATS_QUEUE_SIZE;
                OutgoingBPS = (int)(SentBytes - old_out) / Settings.STATS_QUEUE_SIZE;
                /*                              Log("Incoming: "+IncomingBPS +" Out: "+OutgoingBPS +
                                                            " Lag: "+LastLag+" Pings: "+ReceivedPongs +
                                                            "/"+SentPings, LogLevel.Debug); 
                */
            }
        }

        /// <summary>
        /// I miss my huge red box.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ea"></param>
        private void PingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ea)
        {
            if (!ProcessPackets)
            {
                Log("PingTimer left running after disconection on " + Sim.ToString(), LogLevel.Warning);
                PingTimer.Stop();
                return;
            }
      //      Log("PING", LogLevel.Debug);

            SendPing();
            SentPings++;
        }

        /// <summary>
        /// Returns Simulator Name as a String
        /// Umm, this shouldn't be used, it's Simulator.ToString() that we want.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            if (Sim.Name.Length > 0)
                return Sim.Name + " (" + ipEndPoint.ToString() + ")";
            else
                return "(" + ipEndPoint.ToString() + ")";
        }

        // FIXME: Need a GetHashCode that converts EndPoint.

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
      //  public override int GetHashCode()
      //  {
      //      return Sim.ID.GetHashCode();
      //  }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            SimulatorConnection simLink = obj as SimulatorConnection;
            if (simLink == null)
                return false;
            return endPoint.Equals(simLink.endPoint);
        }

        public void SendPing()
        {
            StartPingCheckPacket ping = new StartPingCheckPacket();
            ping.PingID.PingID = LastPingID++;
            ping.PingID.OldestUnacked = 0; // FIXME
            ping.Header.Reliable = false;
            SendPacket(ping, true);
            LastPingSent = Environment.TickCount;
        }

        /// <summary>
        /// FIXME: hook into outbound queue. 
        /// Instead of sleeping we should be using a Queue<Packet>
        /// type setup. Sleeping a thread that does bi-directional IO is Bad Juju!
        /// No wonder we're loosing packets right and left under heavy loads.
        /// </summary>
        public void DoThrottle()
        {
            float throttle;
            if (OutgoingBPS > Settings.OUTBOUND_THROTTLE_RATE)
            {
                throttle = (OutgoingBPS - Settings.OUTBOUND_THROTTLE_RATE) * 2000 /
                                         Settings.OUTBOUND_THROTTLE_RATE;
                //                              Log("Throttling "+throttle, LogLevel.Debug);
                Thread.Sleep((int)throttle);
            }
        }

        /// <summary>
        /// Sends out pending acknowledgements
        /// </summary>
        private void SendAcks()
        {
         //   Log("SendAcks: lock PendingAcks", LogLevel.Warning);
            if (!ProcessPackets)
                return;
            lock (PendingAcks)
            {
                if (PendingAcks.Count > 0)
                {
                //    string Acks = "";
                    if (PendingAcks.Count > 250)
                    {
                        // FIXME: Handle the odd case where we have too many pending ACKs queued up
                        Log("Too many ACKs queued up!", LogLevel.Error);
                        return;
                    }

                    PacketAckPacket acks = new PacketAckPacket();
                    acks.Header.Reliable = false;
                    acks.Packets = new PacketAckPacket.PacketsBlock[PendingAcks.Count];

                    for (int i = 0; i < PendingAcks.Count; i++)
                    {
                        acks.Packets[i] = new PacketAckPacket.PacketsBlock();
                        acks.Packets[i].ID = PendingAcks.Values[i];
              //          Acks += " " + PendingAcks.Values[i].ToString();
                    }
               //     Log("Sending acks: " + Acks, LogLevel.Debug);
                    SendPacket(acks, true);

                    PendingAcks.Clear();
                }
            }
         //   Log("SendAcks: release PendingAcks", LogLevel.Warning);
        }

        /// <summary>
        /// Resend unacknowledged packets
        /// </summary>
        private void ResendUnacked()
        {
            int now = Environment.TickCount;

         //   Log("REsendUnacked: Lock NeedAck", LogLevel.Warning);
            if (!ProcessPackets)
                return;
            lock (NeedAck)
            {
                foreach (Packet packet in NeedAck.Values)
                {
                    if (now - packet.TickCount > Settings.RESEND_TIMEOUT)
                    {
                        try
                        {
                            Log("Resending " + packet.Type.ToString() + " packet (" + packet.Header.Sequence +
                                "), " + (now - packet.TickCount) + "ms have passed", LogLevel.Warning);
                            packet.Header.Resent = true;
                            ResentPackets++;
                            SendPacket(packet, false);
                        }
                        catch (Exception ex)
                        {
                            Log("got exception trying to resend packet: " + ex.ToString(), LogLevel.Error);
                            Disconnect();
                        }
                    }
                }
            }
         //   Log("ReSendUnacked: release NeedAck", LogLevel.Warning);
        }

        public void RegionHandshakeHandler(Packet packet)
        {
            RegionHandshakePacket handshake = (RegionHandshakePacket)packet;
            Sim.ID = handshake.RegionInfo.CacheID;
            Sim.IsEstateManager = handshake.RegionInfo.IsEstateManager;
            Sim.Name = Helpers.FieldToUTF8String(handshake.RegionInfo.SimName);
            Sim.SimOwner = handshake.RegionInfo.SimOwner;
            Sim.TerrainBase0 = handshake.RegionInfo.TerrainBase0;
            Sim.TerrainBase1 = handshake.RegionInfo.TerrainBase1;
            Sim.TerrainBase2 = handshake.RegionInfo.TerrainBase2;
            Sim.TerrainBase3 = handshake.RegionInfo.TerrainBase3;
            Sim.TerrainDetail0 = handshake.RegionInfo.TerrainDetail0;
            Sim.TerrainDetail1 = handshake.RegionInfo.TerrainDetail1;
            Sim.TerrainDetail2 = handshake.RegionInfo.TerrainDetail2;
            Sim.TerrainDetail3 = handshake.RegionInfo.TerrainDetail3;
            Sim.TerrainHeightRange00 = handshake.RegionInfo.TerrainHeightRange00;
            Sim.TerrainHeightRange01 = handshake.RegionInfo.TerrainHeightRange01;
            Sim.TerrainHeightRange10 = handshake.RegionInfo.TerrainHeightRange10;
            Sim.TerrainHeightRange11 = handshake.RegionInfo.TerrainHeightRange11;
            Sim.TerrainStartHeight00 = handshake.RegionInfo.TerrainStartHeight00;
            Sim.TerrainStartHeight01 = handshake.RegionInfo.TerrainStartHeight01;
            Sim.TerrainStartHeight10 = handshake.RegionInfo.TerrainStartHeight10;
            Sim.TerrainStartHeight11 = handshake.RegionInfo.TerrainStartHeight11;
            Sim.WaterHeight = handshake.RegionInfo.WaterHeight;

            Log("Received a region handshake for " + Sim.ToString(), LogLevel.Info);

            // unlike libsl we'll handle connection completion here where it's an apropriate response to the handshake.
            // Send a RegionHandshakeReply
            RegionHandshakeReplyPacket reply = new RegionHandshakeReplyPacket();
            reply.AgentData.AgentID = Grid.AgentID;
            reply.AgentData.SessionID = Grid.SessionID;
            reply.RegionInfo.Flags = 0;
            SendPacket(reply);

            // We're officially connected to this sim
            Connected = true;
            // Start a timers that check if we've been disconnected
            AckTimer.Start();
            StatsTimer.Start();
            PingTimer.Enabled = Settings.SEND_PINGS;
            ConnectionTimeout.Start();
            PingTimer.Start();


            // If enabled, send an AgentThrottle packet to the server to increase our bandwidth
            if (Settings.SEND_AGENT_THROTTLE)
            {
                Grid.Throttle.Set(this);
                Log("Throttling connection to " + Grid.Throttle.ToString(), LogLevel.Debug);
            }
            // let the GridManager know that we completed our connection so that it can update management info.

            ConnectedEvent.Set();
            MQueue.MatchRegistered(Events.Connected, AckMessage.StatusType.Completed, this);
            Grid.SimConnected(Sim);
        }

        private void StartPingCheckHandler(Packet packet)
        {
            StartPingCheckPacket incomingPing = (StartPingCheckPacket)packet;
            CompletePingCheckPacket ping = new CompletePingCheckPacket();
            ping.PingID.PingID = incomingPing.PingID.PingID;
            ping.Header.Reliable = false;
            // TODO: We can use OldestUnacked to correct transmission errors
            //   I don't think that's right.  As far as I can tell, the Viewer
            //   only uses this to prune its duplicate-checking buffer. -bushing
            SendPacket(ping);
    //        Log("PING " + Sim.ToString(), LogLevel.Debug);
        }

        private void PongHandler(Packet packet)
        {
            CompletePingCheckPacket pong = (CompletePingCheckPacket)packet;
            String retval = "Pong2: " + (Environment.TickCount - LastPingSent);
            if ((pong.PingID.PingID - LastPingID + 1) != 0)
                retval += " (gap of " + (pong.PingID.PingID - LastPingID + 1) + ")";

            LastLag = Environment.TickCount - LastPingSent;
            ReceivedPongs++;
      //      Log("PONG " + Sim.ToString(), LogLevel.Debug);
            DisconnectCandidate = false;
        }

        private void ParcelOverlayHandler(Packet packet)
        {
            ParcelOverlayPacket overlay = (ParcelOverlayPacket)packet; 
            if (overlay.ParcelData.SequenceID >= 0 && overlay.ParcelData.SequenceID <= 3)
            {
                Array.Copy(overlay.ParcelData.Data, 0, Sim.ParcelOverlay,
                    overlay.ParcelData.SequenceID * 1024, 1024);
                Sim.ParcelOverlaysReceived++;

                if (Sim.ParcelOverlaysReceived > 3)
                {
                    // TODO: ParcelOverlaysReceived should become internal, and reset to zero every 
                    // time it hits four. Also need a callback here
                }
            }
            else
            {
                Log("Parcel overlay with sequence ID of " + overlay.ParcelData.SequenceID +
                    " received from " + ToString(), LogLevel.Warning);
            }
        }

        public void SimStatsHandler(Packet packet)
        {
          //  Log("Got sim stats msg. " + ((SimStatsPacket)carrier.packet).ToString(), LogLevel.Info);
        }

        /// <summary>
        /// Get sim time from the appropriate packet
        /// </summary>
        /// <param name="packet">Incoming SimulatorViewerTimeMessagePacket from SL</param>
        /// <param name="simulator">Unused</param>
        private void TimeMessageHandler(Packet packet)
        {
            SimulatorViewerTimeMessagePacket time = (SimulatorViewerTimeMessagePacket)packet;

            Sim.sunPhase = time.TimeInfo.SunPhase;
            Sim.sunDirection = time.TimeInfo.SunDirection;
            Sim.sunAngVelocity = time.TimeInfo.SunAngVelocity;

            // TODO: Does anyone have a use for the time stuff?
        }

        void MovementCompleteHandler(Packet packet)
        {
            AgentMovementCompletePacket movement = (AgentMovementCompletePacket)packet;
            Sim.Handle = movement.Data.RegionHandle;
        }

        protected void CoarseLocationUpdateHandler(Packet packet)
        {
            CoarseLocationUpdatePacket coarse = (CoarseLocationUpdatePacket)packet;

            lock (Sim.avatarPositions)
            {
                Sim.avatarPositions.Clear();

                for (int i = 0; i < coarse.Location.Length; i++)
                {
                    if (i == coarse.Index.Prey)
                    {
                        // TODO: Handle the coarse target position
                    }
                    else if (i != coarse.Index.You)
                    {
                        Sim.avatarPositions.Add(new LLVector3(coarse.Location[i].X, coarse.Location[i].Y,
                            coarse.Location[i].Z));
                    }
                }
            }
        }


    }
}
